﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.run;
using xplang.abstractions;
using xplang.expression;
using xplang.errors;
using xplang.contexts;
using xplang.fs;
using xplang.rtl;
using xplang.statements;

namespace xplang.parse
{

  public class CompilerForExpression : CompilerForImplementatedCode
  {

    public CompilerForExpression(ClassDecl declaringClass, ClassMethod method, Token destination, ClassRef resultType, Compiler parentCompiler, ImplementationCode code, CompilerForExpressionSupport support)
      : base(declaringClass, parentCompiler, code)
    {
      this.method = method;
      this.destination = destination;
      this.resultType = resultType;
      this.support = support;
    }

    public readonly ClassMethod method;
    public readonly Token destination;
    public readonly ClassRef resultType;
    public readonly CompilerForExpressionSupport support;
    public override OpCode CompileImplementatedCode(ImplementationCode code)
    {
      Expression expr = null;
      compileExpression(ref expr);
      checkEof(true);
      return expr;
    }

    public bool compileExpression(ref Expression expr)
    {
      Expression e = compileBasicPreOrPostExpression();
      if (isEof() || current.prec < 0) // nao é operador
        return validate(e, ref expr);
      Token op1 = current;
      nextToken();
      Expression right = compileBasicExpression();
      OpExpression ope = new OpExpression(e, right, op1);

      while (!isEof() && current.prec >= 0)
      {
        Token op2 = current;
        nextToken();
        e = compileBasicExpression();
        ope = opPrec(ope, op2, e);
      }
      return validate(ope.opGen(), ref expr);
    }

    private bool validate(Expression desired, ref Expression exp)
    {
      exp = desired;
      if (desired != null && resultType != null)
      {
        if (!resultType.supports(exp.resultType, null))
        {
          new IncompatibleTypesError(destination, resultType, exp.resultType);
          return true;
        }
      }
      return true;
    }

    private OpExpression opPrec(OpExpression prior, Token op, Expression exp)
    {
      if (op.prec < prior.op.prec)
      {
        OpExpression sub = new OpExpression(prior.right, exp, op);
        prior.right = sub;
        return prior;
      }
      return new OpExpression(prior, exp, op);
    }

    private Expression compileBasicPreOrPostExpression()
    {
      Expression e;
      if ((current.prec == Token.PreOperator) || (current.prec == Token.PreOrPostOperator))
        e = compilePreOperator();
      else
      {
        Token exptoken = current;
        e = compileBasicExpression();
        while ((e != null) && ((current.prec == Token.PostOperator) || (current.prec == Token.PreOrPostOperator)))
          e = compilePostOperator(e, exptoken);
      }
      return e;
    }

    private Expression compilePreOperator()
    {
      //if (isOperator("!"))
      //{
      //  e = compileBasicExpression();
      //  if (!xplang.rtl.RTL.booleanClass.getRef(null).supports(e.resultType, null))
      //    new IncompatibleTypesError(destination, resultType, e.resultType);
      //  e=new 
      //}
      return null;
    }

    private Expression compilePostOperator(Expression exp, Token expToken)
    {
      if (isOperator("."))
      {
        nextToken();
        if (current.type == TokenType.ttFunction)
          return compileFunction(exp);
        else if (isIdentifier())
        {
          Variable v = exp.resultType.getClassDecl().findAttribute(current.text, true);
          if (v == null)
          {
            new IdentifierExpectedError(current);
            return null;
          }
          nextToken();
          return new ExternalAttributeReference(exp, v);
        }
        return null;
      }
      else if (isOperator("=") && support.Attrib)
      {
        if (!(exp is ValueReference))
          new VariableExpectedError(expToken);
        ValueReference varRef = (ValueReference)exp;
        nextToken();
        Expression expr = null;
        CompilerForExpression comp = new CompilerForExpression(declaringClass, method, expToken, varRef.resultType, this, null, CompilerForExpressionSupport.ExpressionWithAttribs());
        if (!comp.compileExpression(ref expr))
          return null;
        return new Attrib(varRef, expr, varRef.byRef());
      }
      else if (isOperator("++") && support.Attrib)
      {
        if (!(exp is ValueReference))
          new VariableExpectedError(expToken);
        ValueReference varRef = (ValueReference)exp;
        if (varRef.resultType.findMethodImplementation(xplang.rtl.RTL.incrementableClass.mInc) == null)
        {
          new UnsuportedOperationError(varRef.resultType, null, current);
          return null;
        }
        nextToken();
        return new PostOperatorInvoke(varRef, xplang.rtl.RTL.incrementableClass.mInc);
      }
      else if (isOperator("--") && support.Attrib)
      {
        if (!(exp is ValueReference))
          new VariableExpectedError(expToken);
        ValueReference varRef = (ValueReference)exp;
        if (varRef.resultType.findMethodImplementation(xplang.rtl.RTL.decrementableClass.mDec) == null)
        {
          new UnsuportedOperationError(varRef.resultType, null, current);
          return null;
        }
        nextToken();
        return new PostOperatorInvoke(varRef, xplang.rtl.RTL.decrementableClass.mDec);
      }
      else if (isOperator("+=") && support.Attrib)
      {
        if (!(exp is ValueReference))
          new VariableExpectedError(expToken);
        ValueReference varRef = (ValueReference)exp;
        nextToken();
        Expression expr = null;
        CompilerForExpression comp = new CompilerForExpression(declaringClass, method, expToken, varRef.resultType, this, null, CompilerForExpressionSupport.ExpressionWithAttribs());
        if (!comp.compileExpression(ref expr))
          return null;
        if (varRef.resultType.findMethodImplementation(xplang.rtl.RTL.addableClass.mAdd) == null)
        {
          new UnsuportedOperationError(varRef.resultType, expr.resultType, current);
          return null;
        }
        return new SelfAssignOperator(varRef, xplang.rtl.RTL.addableClass.mAdd, expr);
      }
      return null;
    }

    private Expression compileBasicExpression()
    {
      Token start = current;
      Expression e = tryCompileBasicExpression();
      if (e == null)
        new ExpressionExpectedError(start);
      return e;
    }

    private Expression tryCompileBasicExpression()
    {
      Token start = current;
      Variable var = null;
      int varindex;
      if (isEof() && isOperator(";") && isOperator(")"))
        return null;
      if (current.type == TokenType.ttIntNumber)
      {
        nextToken();
        return new Const(new Value(xplang.rtl.RTL.intClass.getRef(null), Convert.ToInt64(start.text)));
      }
      if (current.type == TokenType.ttDecNumber)
      {
        nextToken();
        return new Const(new Value(xplang.rtl.RTL.doubleClass.getRef(null), Convert.ToDouble(start.text, FileSystem.formatProvider)));
      }
      if (isIdentifier())
      {
        if ((varindex = method.findVariable(current.text, ref var)) != -1)
        {
          nextToken();
          return new LocalVariableReference(var, varindex);
        }
        var = declaringClass.findAttribute(current.text, true);
        if (var != null)
        {
          nextToken();
          return new InternalAttributeReference(var);
        }
        Value obj;
        if (declaringClass.context.objects.TryGetValue(current.text, out obj))
        {
          nextToken();
          return new ContextObjectReference(obj);
        }
      }
      if (support.Statements)
      {
        Statement stmt = null;
        if (compileStatement(ref stmt))
          return stmt;
      }
      //if (isIdentifier() && ((var = method.declaringClass.findAttribute(current.text, true)) != null))
      //{
      //  nextToken();
      //  return new obj ReadVariable(var);
      //}
      // this & qualified variable
      return null;
    }

    private bool compileStatement(ref Statement stmt)
    {
      if (!support.Statements)
        return false;
      bool block = false;
      Token start = current;
      bool ok = false;
      ClassRef c = null;
      Variable v = null;
      if (isClause("return") && support.Return)
        ok = compileReturn(ref stmt);
      else if (isClause("break") && support.Break)
      {
        block = true;
        ok = compileBreak(ref stmt);
      }
      else if (isClause("while"))
      {
        block = true;
        ok = compileWhile(ref stmt);
      }
      else if (isClause("if"))
      {
        block = true;
        ok = compileIf(ref stmt);
      }
      else if (tryCompileTypeReference(declaringClass, ref c) && support.LocalVariables)
      {
        ok = createLocalVariable(ref stmt, c);
      }
      // self & qident
      if (stmt == null || !ok)
      {
        new StatementExpectedError(current);
        ok = false;
      }
      if (isEof())
      {
        new StatementBlockExpectedError(start);
        ok = false;
      }
      else
        if (ok && !block)
        {
          if (!checkOperator(";"))
          {
            new OperatorExpectedError(current);
            ok = false;
          }
        }
      return ok;
    }

    private bool createLocalVariable(ref Statement stmt, ClassRef type)
    {
      while (isOperator("["))
      {
        throw new Exception("todo");
      }
      Token name = readIdentifier();
      Variable v = null;
      if (method.findVariable(name.text, ref v) != -1)
      {
        new DuplicateElementError(name);
        return false;
      }
      Expression init = null;

      if (isOperator("="))
      {
        nextToken();
        CompilerForExpression comp = new CompilerForExpression(declaringClass, method, name, type, this, null, CompilerForExpressionSupport.ExpressionWithoutAttribs());
        if (!comp.compileExpression(ref init))
          return false;
      }
      else
        new OperatorExpectedError(current); // inicialização obrigatório (por enquanto)
      if (!checkOperator(";"))
        return false;
      v = new Variable(Modifier.None, name.text, type, init);
      method.locals.Add(v);
      stmt = new Attrib(new LocalVariableReference(v, method.parameters.Length + method.locals.Count), init, false);
      return true;
    }

    private bool compileReturn(ref Statement stmt)
    {
      Token dest = current;
      Expression expr = null;
      if (method.result.type == xplang.rtl.RTL.voidClass.getRef(null))
        nextToken();
      else
      {
        nextToken();
        CompilerForExpression comp = new CompilerForExpression(declaringClass, method, dest, method.result.type, this, null, CompilerForExpressionSupport.ExpressionWithoutAttribs());
        if (!comp.compileExpression(ref expr))
          return false;
      }
      stmt = new Return(expr);
      return checkOperator(";");
    }

    private bool compileBreak(ref Statement stmt)
    {
      nextToken();
      stmt = new Break();
      return true;
    }

    private bool compileWhile(ref Statement stmt)
    {
      Token dest = current;
      Expression expr = null;
      nextToken();
      checkOperator("(");
      nextToken();
      CompilerForExpression compE = new CompilerForExpression(declaringClass, method, dest, xplang.rtl.RTL.booleanClass.getRef(null), this, null, CompilerForExpressionSupport.ExpressionWithoutAttribs());
      if (!compE.compileExpression(ref expr))
        return false;
      checkOperator(")");
      nextToken();
      Expression whileStmts = null;
      CompilerForStatements compS = new CompilerForStatements(method, declaringClass, dest, this, null, CompilerForExpressionSupport.BreakableStatement());
      if (!compS.compileStatements(ref whileStmts, true, true) || whileStmts == null)
        return false;
      stmt = new While(expr, whileStmts);
      return true;
    }

    private bool compileIf(ref Statement stmt)
    {
      Token dest = current;
      Expression expr = null;
      nextToken();
      checkOperator("(");
      nextToken();
      CompilerForExpression compE = new CompilerForExpression(declaringClass, method, dest, xplang.rtl.RTL.booleanClass.getRef(null), this, null, CompilerForExpressionSupport.ExpressionWithoutAttribs());
      if (!compE.compileExpression(ref expr))
        return false;
      checkOperator("(");
      nextToken();
      Expression stmtsTrue = null;
      CompilerForStatements compT = new CompilerForStatements(method, declaringClass, dest, this, null, CompilerForExpressionSupport.MethodStatement());
      if (!compT.compileStatements(ref stmtsTrue, true, true))
        return false;
      Expression stmtsFalse = null;
      if (isClause("else"))
      {
        nextToken();
        CompilerForStatements compF = new CompilerForStatements(method, declaringClass, dest, this, null, CompilerForExpressionSupport.MethodStatement());
        if (!compF.compileStatements(ref stmtsFalse, true, true))
          return false;
      }
      stmt = new If(expr, stmtsTrue, stmtsFalse);
      return true;
    }

    private Expression compileFunction(Expression _this)
    {
      Token name = current;
      if (name.type != TokenType.ttFunction)
      {
        new FunctionExpectedError(name);
        return null;
      }
      nextToken();
      if (!checkOperator("("))
        return null;
      nextToken();
      List<Expression> parameters = new List<Expression>();
      while (!isOperator(")"))
      {
        CompilerForExpression comp = new CompilerForExpression(declaringClass, method, current, null, this, null, CompilerForExpressionSupport.ExpressionWithoutAttribs());
        Expression param = null;
        comp.compileExpression(ref param);
        parameters.Add(param);
        if (!isOperator(")"))
        {
          if (checkOperator(","))
            nextToken();
        }
      }
      checkOperator(")");
      nextToken();
      int n = parameters.Count;
      ClassRef[] types = new ClassRef[n];
      for (int i = 0; i < n; i++)
        types[i] = parameters[i].resultType;
      ClassMethod imethod = _this.resultType.getClassDecl().findMethod(name.text, types, new ClassRef[] { }, true);
      return new InvokeExternalMethod(_this, imethod, parameters.ToArray());
    }

  }

  public class ErrExpression : Expression
  {
    public ErrExpression()
      : base(xplang.rtl.RTL.undefinedClass.getRef(null))
    {
    }
    public override Value doIt(Value[] heap, ref FlowState state)
    {
      throw new NotImplementedException(); // só para compilar
    }
  }

  public class OpExpression : Expression
  {
    public OpExpression(Expression left, Expression right, Token op)
      : base(xplang.rtl.RTL.voidClass.getRef(null))
    {
      this.left = left;
      this.right = right;
      this.op = op;
    }

    public Expression left;
    public Expression right;
    public Token op;

    public override Value doIt(Value[] heap, ref FlowState state)
    {
      throw new NotImplementedException(); // só para compilar
    }

    public Expression opGen()
    {
      Expression l = left is OpExpression ? (left as OpExpression).opGen() : left;
      Expression r = right is OpExpression ? (right as OpExpression).opGen() : right;

      ClassMethod m = l.resultType.getClassDecl().findMethod(op.text, new ClassRef[] { r.resultType }, null, true);
      if (m == null)
      {
        new UnsuportedOperationError(l.resultType, r.resultType, op);
        return new ErrExpression();
      }
      MethodImplementation i = l.resultType.findMethodImplementation(m);
      if (i == null)
        new UnsuportedOperationError(l.resultType, r.resultType, op);
      return new ExpressionOp(l, r, m);
    }
  }

  public class CompilerForExpressionSupport
  {
    public bool LocalVariables;
    public bool Attrib;
    public bool Statements;
    public bool Return;
    public bool Break;

    public static CompilerForExpressionSupport MethodStatement()
    {
      return new CompilerForExpressionSupport()
      {
        Statements = true,
        Break = false,
        Return = true,
        LocalVariables = true,
        Attrib = true
      };
    }

    public static CompilerForExpressionSupport BreakableStatement()
    {
      return new CompilerForExpressionSupport()
      {
        Statements = true,
        Break = true,
        Return = true,
        LocalVariables = true,
        Attrib = true
      };
    }

    public static CompilerForExpressionSupport ExpressionWithoutAttribs()
    {
      return new CompilerForExpressionSupport()
      {
        Statements = false,
        Break = false,
        Return = false,
        LocalVariables = false,
        Attrib = false
      };
    }

    public static CompilerForExpressionSupport ExpressionWithAttribs()
    {
      return new CompilerForExpressionSupport()
      {
        Statements = false,
        Break = false,
        Return = false,
        LocalVariables = false,
        Attrib = true
      };
    }
  }
}
